package com.societyGames.codeAlong.state.codeExercise.view
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;

public class DirectionArrow extends Sprite
{
  private var _arrowContainer:Sprite = new Sprite(); //This is what we add to render tree.

  private var _arrowParent:DisplayObjectContainer; //Who are we attached to?
  private var _targetDisplayObject:DisplayObject; //Who are we pointing at?
  private var _targetX:Number;
  private var _targetY:Number;
  private var _targetWidth:Number;
  private var _targetHeight:Number;

  public function DirectionArrow(arrow:DisplayObject, offsetX:Number, offsetY:Number)
  {
    arrow.x = offsetX;
    arrow.y = offsetY;
    this._arrowContainer.addChild(arrow);
    this.addChild(this._arrowContainer);
  }

  public function show(arrowParent:DisplayObjectContainer, targetDisplayObject:DisplayObject):void
  {
    this._arrowParent = arrowParent;
    this._targetDisplayObject = targetDisplayObject;

    turnOn();
  }

  public function hide():void
  {
    turnOff();
    this._arrowParent = null;
    this._targetDisplayObject = null;
  }

  private function turnOn():void
  {
    addGraphics();
    addHandlers();
  }

  private function turnOff():void
  {
    removeHandlers();
    removeGraphics();
  }

  private function addHandlers():void
  {
    this.addEventListener(Event.ENTER_FRAME, enterFrameHandler, false, 0, true);
    this.stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false, 0, true);
    //Require a move before the roll over handler becomes active.
    this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, 0, true);
  }

  private function removeHandlers():void
  {
    this.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
    this.stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    this.removeEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
  }

  private function removeGraphics():void
  {
    //If we drew a rectangle, get rid of it.
    this.graphics.clear();
    //Remove ourselves
    if (this.parent != null)
    {
      this.parent.removeChild(this);
    }
  }

  private function drawRectangle(bounds:Rectangle):void
  {
    //Draw red rectangle
    this.graphics.lineStyle(3, 0x0000FF);
    this.graphics.beginFill(0, 0);
    this.graphics.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
    this.graphics.endFill();
  }

  private function pointArrowAt(bounds:Rectangle):void
  {
    //Calculate the center point
    var center:Point = new Point(bounds.left + (bounds.right - bounds.left) / 2, bounds.top + (bounds.bottom - bounds.top) / 2);

    //Calculate the slope/intercept
    var pointX:Number;
    var pointY:Number;

    var originX:Number;
    var originY:Number;

    var stage:Stage = _arrowParent.stage;
    if (stage)
    {
      originX = stage.stageWidth / 2;
      originY = stage.stageHeight / 2;
    }
    else
    {
      originX = 0;
      originY = 0;
    }

    //Origin is inside rectangle.
    if (bounds.contains(originX, originY))
    {
      pointX = center.x;
      pointY = center.y;
    }
    //Straight up and down.
    else if (Math.abs(originX - center.x) < 0.000001)
    {
      pointX = center.x;
      pointY = Math.abs(bounds.top - originY) < Math.abs(bounds.bottom - originY) ? bounds.top : bounds.bottom;
    }
    else
    {
      var m:Number = (originY - center.y) / (originX - center.x);
      var b:Number = originY - (m * originX);

      //Calculate the intersect
      //Try horizontal first
      pointY = Math.abs(bounds.top - originY) < Math.abs(bounds.bottom - originY) ? bounds.top : bounds.bottom;
      pointX = (pointY - b) / m;
      //If it's in bounds, use it.
      if (pointX < bounds.left || pointX > bounds.right)
      {
        //Try vertical.
        pointX = Math.abs(bounds.left - originX) < Math.abs(bounds.right - originX) ? bounds.left : bounds.right;
        pointY = (m * pointX) + b;
        //Somehow this is invalid too. Point at the middle.
        if (pointY < bounds.top || pointY > bounds.bottom)
        {
          pointX = center.x;
          pointY = center.y;
        }
      }
    }
    rotateArrow(_arrowParent, originX, originY, pointX, pointY);
  }

  private function addGraphics():void
  {
    //Calculate the center of the display object
    var bounds:Rectangle = _targetDisplayObject.getBounds(_arrowParent);
    drawRectangle(bounds);
    pointArrowAt(bounds);
    storeDisplayObjectRect();
  }

  //We always dynamically add ourselves to the parent, so we appear on top of rendering layer.
  private function rotateArrow(parent:DisplayObjectContainer, originX:Number, originY:Number, pointX:Number, pointY:Number):void
  {
    this._arrowContainer.x = pointX;
    this._arrowContainer.y = pointY;
    this._arrowContainer.rotation = Math.atan2(pointY - originY, pointX - originX) * (180 / Math.PI) + 90;
    parent.addChild(this);
  }

  private function storeDisplayObjectRect():void
  {
    _targetX = _targetDisplayObject.x;
    _targetY = _targetDisplayObject.y;
    _targetWidth = _targetDisplayObject.width;
    _targetHeight = _targetDisplayObject.height;
  }

  private function enterFrameHandler(event:Event):void
  {
    if (_targetDisplayObject.x != _targetX ||
            _targetDisplayObject.y != _targetY ||
            _targetDisplayObject.width != _targetWidth ||
            _targetDisplayObject.height != _targetHeight)
    {
      turnOff();
      turnOn();
    }
  }

  private function mouseMoveHandler(event:MouseEvent):void
  {
    this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    //If we're already over the arrow, just hide right away
    if (this.hitTestPoint(event.stageX, event.stageY, true))
    {
      hide();
    }
    else //Otherwise, wait till we're over it.
    {
      this.addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
    }
  }

  private function mouseDownHandler(event:MouseEvent):void
  {
    hide();
  }

  //TODO: Move this to mouse move handler with point check to prevent it from appearing under the mouse?
  private function rollOverHandler(event:MouseEvent):void
  {
    hide();
  }
}
}
